/**
 * Package: vvg.lib.hc
 *
 * HighCharts related constants and library functions.
 * - create the custom chart with vvg.lib.hc.createBoreChart(...)
 * - change hc.fields and hc.tooltipFields to use a different data model
 * - change just hc.fields.*Colours to use different colours on the chart
 * 
 * Relies on:
 * - vvg.lib
 * - vvg.lib.bore
 * - Highcharts
 * 
 * @author: King Lung Chiu
 */
(function(vvg){
   
   var app =  vvg.app;
   var lib = vvg.lib;
   var libBore = lib.bore;
   var dbg = vvg.dbg = vvg.dbg || {};
   
   var VIZ_BORE_SELECTED = lib.eventNames.VIZ_BORE_SELECTED;
   var VIZ_BORE_UNSELECTED = lib.eventNames.VIZ_BORE_UNSELECTED;
   
   app.hcCharts = [];
   app.hcChartCount = 0;

   dbg.generateSampleWFSData = function(){
      var myTransect = libBore.boreTransect(libBore.defaultBoreDescriptor);

      var i,
         bore,
         dates = ['2010-01-02 9:22', '2011-02-03 12:01',  '2012-03-04 22:24'],
         count = Math.random() * 10 + 10;
//      Math.seedrandom('fdsafjklwej');
      for (i = 0; i<count; i += 1) {
//      for (i = 0; i<18; i += 1) {
         bore = {};
         bore.label = 'Bore ' + i.toString();
         bore.linearDistance = Math.random()*120 - 20;
//         bore.linearDistance = Math.random()*12000.0 - 2000;
//         bore.linearDistance = Math.random()*10000.0;
//         bore.linearDistance = i*1000.0;
         bore.elevation = Math.random()*100.0+200.0;
         bore.offset = Math.random()*1000.0-500.0;
         bore.depth = Math.random()*80.0+10.0;
         bore.groundwaterLevel = Math.max(bore.depth-(Math.random()*10.0), 0.0);
         //bore.groundwaterLevelDate = dates[Math.floor(Math.random()*(dates.length))];
         bore.groundwaterLevelDate = dates[i % dates.length];
         bore.eh = Math.random()*0.1;
         // Make some bores incomplete:
//         if (i === 0 || i === 1) {
//            delete bore.label;
//            bore.elevation = 0.0;
//         }
         myTransect.addBore(bore);
      }
      
      return myTransect.getBores();
   };
   
   dbg.generateSampleAreaChartData = function() {
      var i = 0;
      var bores = dbg.generateSampleWFSData();
      
      lib.log('\n[lib.hc/app.generateSampleAreaChartData]');
      for(i = 0; i < bores.length; i++) {
         // simplify display of x-axis values
//         bores[i].linearDistance = Math.round(bores[i].linearDistance);
         
         // adjust all to same reference point (DEM)
         bores[i].depth = bores[i].elevation - bores[i].depth;
         bores[i].groundwaterLevel = bores[i].elevation - bores[i].groundwaterLevel;
         
         // cumulative values for stacked area chart
         bores[i].depth_a = bores[i].depth;
         bores[i].groundwaterLevel_a = bores[i].groundwaterLevel - bores[i].depth;
         bores[i].elevation_a = bores[i].elevation - bores[i].groundwaterLevel;

         // dummy data to test line chart with numeric x & y
         bores[i].line = bores[i].depth * 0.75;
         
         lib.log(
         '('+
            bores[i].linearDistance +',\n\t'+
            bores[i].depth +', '+
            bores[i].groundwaterLevel +', '+
            bores[i].elevation +',\n\t'+
            bores[i].depth_a +', '+
            bores[i].groundwaterLevel_a +', '+
            bores[i].elevation_a
            +')');
      }
      
      return bores;
 
   };
   
   var hc = lib.hc = {
      
      /**
       * Specifies how the properties from vizData will be plotted on a
       * 2D chart by default.
       * - x:
       *   the property that will be plotted against the x-axis
       * - stacked:
       *   the properties and order of them that'll be used as y-values
       *   for a stacked area chart
       * - stackedLegends:
       *   the labels for the corresponding stacked properties that'll be
       *   displayed in the chart's legends
       */
      fields: {
         x: 'linearDistance'
         ,id: 'bore_id'
         ,stacked: ['ac_elevation', 'ac_aquiferTop', 'ac_aquiferBottom']
         ,stackedLegends: ['Top Strata', 'Aquifer', 'Sub Strata']
         ,stackedColours: ['#AA4643', '#4572A7', ['#49552E', '#89a54e']]
//         ,stackedColours: ['#AA4643', '#4572A7', '#89A54E']
//         ,stackedColours: ['#ffcc66', '#4572f7', '#706050']
//         ,stackedColours: ['#ffcc66', '#4572a7', '#706050']
//         ,stackedColours: ['#CC6633', '#058DC7', '#706050']
//         ,stackedColours: ['#FF9655', '#058DC7', '#A0A0A0']
//         ,stackedColours: ['#FF9655', '#4572A7', '#A0A0A0']
//         ,stackedColours: ['#cc6633', '#4572A7', '#']
         ,scatter: ['groundwaterLevel']
         ,scatterLegends: ['Groundwater Level']
         ,scatterColours: ['#3d96ae']
//         ,scatterColours: ['24CBE5']
         ,column: [['elevation', 'boreDepth']]
         ,columnLegends: ['Borehole']
         ,columnColours: ['#806050']
//         ,columnColours: ['#AA4643']
      }
      ,tooltipFields: [
         {label: 'Bore ID', name: 'bore_id'}
         ,{label: 'Water Level', name: 'groundwaterLevel'}
         ,{label: 'Elevation', name: 'elevation'}
         ,{label: 'Bore Depth', name: 'boreDepth'}
         ,{label: 'Aquifer Top', name: 'aquiferTopLevel'}
         ,{label: 'Aquifer Bottom', name: 'aquiferBottomLevel'}
         ,{label: 'Linear Distance', name: 'linearDistance'}
         ,{label: 'Record Date', name: 'groundwaterLevelDate'}
      ]
      
      
      /**
       * Creates a HighCharts stacked area chart.
       * initialSeries expected to be parsable by generateStackedData()
       */
      ,createBoreChart: function(CHART_DIV, initialSeries, listeners) {
         var chart = new Highcharts.Chart({
            chart: {
               renderTo: CHART_DIV
               ,backgroundColor: '#000000'
//               ,defaultSeriesType: 'area'
//               ,animation: {
//                  duration: 567,   // As close to interval as possible.
//                  easing: 'linear'  // Important: this makes it smooth
//               }
            }
            ,credits: {
               text: 'VeRSI'
               ,href: 'http://www.versi.edu.au'
            }
            ,title: {
               text: 'Cross Section View, Selected Boreholes'
            }
//            ,legend: {
//               align: 'right'
//               ,layout: 'vertical'
//               ,verticalAlign: 'middle'
//            }
            ,plotOptions: {
               area: {
                  stacking: 'normal'
//                  ,lineColor: '#666666'
//                  ,lineWidth: 1
                  ,marker: {
                     enabled: false
                     ,symbol: 'circle'
                     ,radius: 2
//                     lineWidth: 1,
//                     lineColor: '#666666'
                  }
               }
               ,column: {
                  pointWidth: 2
               }
               ,series: {
                  stickyTracking: false
                  ,point: { events: {
                     mouseOver: function() {
                        if(listeners && typeof listeners[VIZ_BORE_SELECTED] === 'function') {
                           listeners[VIZ_BORE_SELECTED](this.id);
                        }
                     }
                     ,mouseOut: function() {
                        if(listeners && typeof listeners[VIZ_BORE_UNSELECTED] === 'function') {
                           listeners[VIZ_BORE_UNSELECTED](this.id);
                        }
                     }
                  }}
               }
            }
            ,tooltip: {
               shared: true,
               useHTML: true,
               formatter: function() {
                  var linearDistance = this.x;
                  var bore = lib.filter(
                     this.points[0].series.chart.vvg_appData.vizData.selectedBores
                     ,function(b){return b.linearDistance === linearDistance;}
                  )[0];
                  var tipFields = hc.tooltipFields;
                  var field = null;
                  var fieldValue = null;
                  var tooltip = null;
                  var i = 0;

                  tooltip = '<table>';
                  for(i = 0; i < tipFields.length; i++){
                     field = tipFields[i];
                     fieldValue = bore[field.name];
                     fieldValue = typeof fieldValue === 'number'?
                        fieldValue.toFixed(2) + 'm': fieldValue;
                     tooltip +=
                        '<tr><td><b>'+ field.label +': </b></td>' +
                        '<td style="text-align: right">'+ fieldValue +'</td></tr>';
//                     tooltip +=
//                        '<tr><td>'+ field.label +': </td>' +
//                        '<td style="text-align: right"><b>'+ fieldValue +'</b></td></tr>';
                  }
                  tooltip += '</table>';
                     
                  return tooltip;
               }
               ,crosshairs: true
            }
            ,xAxis: {
               endOnTick: false
//               endOnTick: true
               ,title: {
                  text: 'Linear Distance (m from left to right)'
               }
            }
            ,yAxis: {
               title: {
                  text: 'Height (m)'
               }
               ,max: 200
               ,min: 0
//               ,endOnTick: true
//               ,startOnTick: false
               ,endOnTick: false
            }
            ,series: hc.generateChartSeries(initialSeries)
         });
         
         chart.vvg_appData = {
            vizData: null
         };
         
         /**
          * Updates chart with new visualisation data.
          * 
          * @param vizData
          *    Expected to be an object of the following:
          *
          *    vizData = {
          *       targetTransect: object from vvg.lib.transect()
          *       ,selectedBores: array from vvg.lib.bore.boreTransect().getBores()
          *       ,debugMsg: a string
          *    }
          *
          *    Sample item from selectedBores:
          *    selectedBore[0] = {
          *       aquiferBottomLevel: 219.89999389648398
          *       ,aquiferTopLevel: 223.89999389648398
          *       ,boreDepth: 52
          *       ,elevation: 271.899993896484
          *       ,groundwaterLevel: 231.369998931885
          *       ,groundwaterLevelDate: "2011-04-27T00:00:00+10:00"
          *       ,isComplete: true
          *       ,label: "Bore 5225"
          *       ,linearDistance: 32423.397770207277
          *       ,offset: -1688.8889150931682
          *    }
          */
         chart.vvg_updateData = function(vizData) {
            var selectedBores = vizData.selectedBores;
            var targetTransect = vizData.targetTransect;
            var point1 = targetTransect.lineString.components[0];
            var point2 = targetTransect.lineString.components[1];
            var transectLength = point1.distanceTo(point2);
            var newSeries;
            var i = 0;
            var yAxis = chart.yAxis[0];
            var xAxis = chart.xAxis[0];
            var yExtremes;

            // record new vizData and generate new series dataset
            this.vvg_appData.vizData = vizData;
            hc.addChartFields(selectedBores);
            newSeries = hc.generateChartSeries(selectedBores);
            
            // update chart with new dataset
            for(i = 0; i < newSeries.length; i++) {
               chart.series[i].setData(newSeries[i].data, false);               
            }
            
            // set new Y-Axis extremes
            yExtremes = hc.calculateYAxisMinMax(selectedBores);
            yAxis.setExtremes(yExtremes.min, yExtremes.max, false);
            
//            // set new X-Axis extremes: fit to full length of transect
//            xAxis.setExtremes(0, Math.ceil(transectLength / 100) * 100, false);
            
            chart.setTitle(null, hc.createSubtitle(selectedBores));
            chart.redraw();
         };
         
         app.hcCharts.push(chart);
         app.hcChartCount += 1;
         
         return chart;
      }
      
      ,createSubtitle: function(selectedBores){
         var i = 0;
         var len = lib.isArray(selectedBores)? selectedBores.length: 0;
         var date;
         var minDate = '9999-99-99',
             maxDate = '0000-00-00';
         
         if(len === 0) {
            return {
               text: 'No records available.'
            };
         } else {
            for(i = 0; i < selectedBores.length; i++){
               date = selectedBores[i]['groundwaterLevelDate'];
               if(date < minDate) {
                  minDate = date;
               } else if(date > maxDate) {
                  maxDate = date;
               }
            }
            return {
              text: len === 1?
                 '(1 record, '+ date.slice(0,10) +')':
                 '('+ len +' records, '+
                    minDate.slice(0,10) +' to '+ maxDate.slice(0,10) +')'
            };
         }
      }
      
      ,addChartFields: function(boreRecords) {
         var bore = null;
         var len = boreRecords.length;
         var i = 0;
         
         for(i = 0; i < len; i++){
            bore = boreRecords[i];
            bore.ac_aquiferBottom = bore.aquiferBottomLevel;
            bore.ac_aquiferTop = bore.aquiferTopLevel - bore.aquiferBottomLevel;
            bore.ac_elevation = bore.elevation - bore.aquiferTopLevel;
         }
      }
      
      ,generateChartSeries: function(selectedBores) {
         var i = 0,
             numScatters = hc.fields.scatter.length,
             numColumns = hc.fields.column.length;
         var field = null;
         var x = hc.fields.x;
         var scatterSeries = [];
         var columnSeries = [];
         var stackedSeries = hc.generateStackedSeries(selectedBores);
         
         for(i = 0; i < numColumns; i++) {
            field = hc.fields.column[i];
            columnSeries.push({
               data: hc.generateFloatingColumnSeriesData(selectedBores, x, field)
               ,type: 'column'
               ,name: hc.fields.columnLegends[i]
               ,color: hc.fields.columnColours[i]
            });            
         }
         for(i = 0; i < numScatters; i++) {
            field = hc.fields.scatter[i];
            scatterSeries.push({
               data: hc.generate2DSeriesData(selectedBores, x, field)
               ,type: 'scatter'
               ,name: hc.fields.scatterLegends[i]
               ,color: hc.fields.scatterColours[i]
            });
         }

         return stackedSeries.concat(columnSeries).concat(scatterSeries);
      }

      ,generateFloatingColumnSeriesData: function(dataset, x, yFields){
         var data = [];
         var top = 0;
         var bottom = 0;
         var record = null;
         var i = 0;
         var len = 0;
         
         if(lib.isArray(dataset)){
            len = dataset.length;
            for(i = 0; i < len; i++){
               record = dataset[i];
               top = record[yFields[0]];
               bottom = top - record[yFields[1]];
               data.push({
                  x: record[x]
                  ,y: top
                  ,low: bottom
               });
            }
            hc.addId(data, dataset);
         }
         
         return data;
      }
      
      /**
       * Converts vizData to the format required by HighChart's
       * stacked area chart.
       * 
       * vizData expected to be array of bore data from
       * boretransect().getBores(),
       * processed with BoreData.js/app.generateSampleAreaChartData()
       * 
       * eg [{
       *    elevation: 100
       *    ,groundwaterLevel: 80
       *    ,depth: 50
       *    ,depth_a: 50
       *    ,groundwaterLevel_a: 30
       *    ,elevation_a: 20
       * }, {
       *    elevation: 105
       *    ,groundwaterLevel: 85
       *    ,depth: 55
       *    ,depth_a: 55
       *    ,groundwaterLevel_a: 30
       *    ,elevation_a: 20
       * }]
       */
      ,generateStackedSeries: function(dataset) {
         var i = 0,
             numStacks = hc.fields.stacked.length;
         var field = null;
         var x = hc.fields.x;
         var stackedSeries = [];
         var colour = null;
         
         for(i = 0; i < numStacks; i++) {
            field = hc.fields.stacked[i];
            stackedSeries.push({
               data: hc.generate2DSeriesData(dataset, x, field)
               ,type: 'area'
               ,name: hc.fields.stackedLegends[i]
               ,allowPointSelect: false
            });
            colour = hc.fields.stackedColours[i];
            switch(lib.typeOf(colour)){
               case 'string':
                  stackedSeries[stackedSeries.length -1].color = colour;
                  break;
               case 'array':
                  stackedSeries[stackedSeries.length -1].color = colour[0];
                  stackedSeries[stackedSeries.length -1].lineColor = colour[1];
                  break;
               default:
                  break;
            }
         }           

         return stackedSeries;
      }
//      ,generateStackedData: function(vizData) {
//         var i = 0, j = 0, count = 0,
//             numStacks = 3;
//         var currentRecord = null;
//         var x = 0;
//         
//         var data = [];
//         for(j = 0; j < numStacks; j++)
//               data.push([]);
//            
//         if(lib.isArray(vizData)) {
//            count = vizData.length;
//            for(i = 0; i < count; i++) {
//               currentRecord = vizData[i];
//               x = currentRecord.linearDistance;
//               data[0][i] = [x, currentRecord.elevation_a];
//               data[1][i] = [x, currentRecord.groundwaterLevel_a];
//               data[2][i] = [x, currentRecord.depth_a];
//            }            
//         }
//
//         return data;
//      }
      
      ,generate2DSeriesData: function(dataset, keyX, keyY) {
         var data = [];
         var i = 0;
         var record = null;
         if(lib.isArray(dataset)){
            for(i = 0; i < dataset.length; i++) {
               record = dataset[i];
               data.push({
                  x: record[keyX]
                  ,y: record[keyY]
               });
            }
            hc.addId(data, dataset);
         }
         return data;
      }
      
      ,addId: function(seriesData, dataset) {
         var i,
             len = dataset.length;
         var idField = hc.fields.id;
         
         for(i = 0; i < len; i++) {
            seriesData[i]['id'] = dataset[i][idField];
         }
      }
      
      ,calculateYAxisMinMax: function(selectedBores){
         var min = Number.MAX_VALUE;
         var max = Number.MIN_VALUE;
         var record = null;
         var i = 0;
         var len;
         
         if(lib.isArray(selectedBores)){
            len = selectedBores.length;
            for(i = 0; i < len; i++){
               record = selectedBores[i];
               min = Math.min(min, record['groundwaterLevel'], record['aquiferBottomLevel']);
               max = Math.max(max, record['elevation'], record['aquiferTopLevel']);
            }
         }
         
         return {
            min: min === Number.MAX_VALUE? 0: (Math.floor(min / 10) * 10 - 10)
            ,max: max === Number.MIN_VALUE? 250: (Math.floor(max / 10) * 10 + 10)
         };
      }
   };
   
}(window.vvg = window.vvg || {}));